<template>
  <div class="games-snake">
    <el-form :inline="true" size="small">
      <el-form-item label="初始长度">
        <el-input-number v-model="userSet.length" :min="1" :max="10" />
      </el-form-item>

      <el-form-item label="移动速度(ms/格)">
        <el-input-number v-model="userSet.speed" :min="200" :max="1000" :step="50" />
      </el-form-item>

      <el-form-item>
        <el-button type="primary" @click="init()">
          初始化
        </el-button>
      </el-form-item>
    </el-form>

    <canvas ref="showNode" />
  </div>
</template>

<script setup lang="ts">
import { reactive, ref, onMounted } from 'vue'
import { global } from '@/types/shimNamespace'

const userSet = reactive({
  length: 3,
  speed: 500
})

const rectSize = 25
const sizeH = 10
const sizeW = 20
const lineWidth = 10
const pointGap = 5
let loopTimer: global.mineTimer = null

interface bodyT {
  x: number
  y: number
}

interface foodT {
  localX: number
  localY: number
}


const direction = {
  horizaontal: 0,
  vertical: 0
}

const snakeBody = <bodyT[]>[]
const pointState = <Array<boolean>[]>[]

let food = <foodT>{}

let showNode = ref<HTMLCanvasElement | null>()
let showCtx = <CanvasRenderingContext2D | null>null

function init() {

  if (loopTimer) {
    gameOver()
  }

  // 清理原始数据
  snakeBody.length = 0
  pointState.length = 0
  direction.horizaontal = 0
  direction.vertical = 0
  food = {
    localX: -1,
    localY: -1
  }

  showCtx!.clearRect(0, 0, showNode.value!.width, showNode.value!.height)

  for (let row = 0; row < sizeH; row++) {
    const localArr = <boolean[]>[]
    pointState.push(localArr)
    for (let col = 0; col < sizeW; col++) {
      localArr.push(true)
    }
  }

  // 初始长度
  let col = 0
  let row = 0
  for (let i = 0; i < userSet.length; i++) {

    setPoint({
      x: row,
      y: col++
    })

    if (col === sizeW) {
      col = 0
      row++
    }
  }

  loopTimer = setInterval(moveSnake, userSet.speed)

  randomFood()

  switchEnable(true)

}

// 游戏结束
function gameOver(tip?: string) {
  clearInterval(<number>loopTimer)
  loopTimer = null
  switchEnable()

  if (tip) {
    alert(tip)
  }

}

// 蛇, 移动
function moveSnake() {
  if (direction.horizaontal === 0 && direction.vertical === 0) {
    return
  }

  const lastPoint = snakeBody[snakeBody.length - 1]

  const nextXNum = lastPoint.x + direction.horizaontal
  const nextYNum = lastPoint.y + direction.vertical

  const nextX = calculate(nextXNum)
  const nextY = calculate(nextYNum)

  if (nextY < 0 || nextY > showNode.value!.width || nextX < 0 || nextX > showNode.value!.height) {
    gameOver('撞墙了')
    return
  }

  for (let join of snakeBody) {

    if (join.x === nextXNum && join.y === nextYNum) {
      gameOver('把自己吃了')
      return
    }
  }


  const foodXS = calculate(food.localX)

  const foodYS = calculate(food.localY)

  if ((nextY === foodYS && nextX === foodXS)) {

    setPoint({
      x: food.localX,
      y: food.localY
    })

    randomFood()

  } else {
    setPoint({
      x: lastPoint.x + direction.horizaontal,
      y: lastPoint.y + direction.vertical
    })


    // 删除一个
    const removePoint = <bodyT>snakeBody.shift()
    setPoint({
      x: removePoint.x,
      y: removePoint.y,
      isClear: true
    })
  }

}

// 绘制颜色块
function setPoint({
  x,
  y,
  isClear = false,
  color = '#000',
  record = true
}: {
  x: number,
  y: number,
  isClear?: boolean,
  color?: string,
  record?: boolean
}) {

  if (isClear) {
    showCtx!.clearRect(
      calculate(y) - lineWidth / 2,
      calculate(x) - lineWidth / 2,
      rectSize + lineWidth,
      rectSize + lineWidth
    )

    if (record) {
      pointState[x][y] = true
    }
  } else {

    if (record) {
      snakeBody.push({ x, y })
      pointState[x][y] = false
    }

    showCtx!.strokeStyle = color
    showCtx!.strokeRect(
      calculate(y),
      calculate(x),
      rectSize,
      rectSize
    )
  }
}

// 计算开始位置
function calculate(n: number) {
  let result = (lineWidth / 2) + (rectSize * n)

  if (n > 0) {
    result += n * (pointGap + lineWidth)
  }
  return result
}

// 更改运动方向
function changeDirection({ keyCode }: { keyCode: number }) {

  if (![87, 38, 83, 40, 65, 37, 68, 39].includes(keyCode)) {
    return
  }

  if (loopTimer === null) {
    init()
  }

  /*
    上: w-87  up-38
    下: s-83  down-40
    左: a-65  left-37
    右: d-68  right-39
  */


  const { horizaontal, vertical } = direction

  let localH = 0
  let localV = 0
  switch (keyCode) {
  case 87:
  case 38:
    localH = horizaontal === 1 ? 1 : -1
    break
  case 83:
  case 40:
    localH = horizaontal === -1 ? -1 : 1
    break
  case 65:
  case 37:
    localV = vertical === 1 ? 1 : -1
    break
  case 68:
  case 39:
    localV = vertical === -1 ? -1 : 1
    break
  default: break
  }

  direction.horizaontal = localH
  direction.vertical = localV

}

// 随机生成食物位置
function randomFood(x = 0) {

  // 判断是否可以放置食物
  if (!pointState.flat().includes(true)) {
    alert('胜利')
    return
  }

  let localX = x
  if (localX === 0) {
    localX = Math.floor(Math.random() * sizeH)
  }

  const localArr = pointState[localX]

  let localY = Math.floor(Math.random() * sizeW)

  if (localArr[localY]) {
    // localX, localY   当前点位可以生成食物
    Object.assign(food, { localX, localY })

    setPoint({
      x: localX,
      y: localY,
      color: 'orange',
      record: false
    })

  } else {

    localY = localArr.findIndex(point => point)

    if (localY) {
      // localX, localY  当前单位可以生成食物
      Object.assign(food, { localX, localY })

      setPoint({
        x: localX,
        y: localY,
        color: 'orange',
        record: false
      })
    } else {
      // 当前行, 没有可放置食物的位置
      randomFood((localX + 1 > sizeH) ? 1 : localX + 1)
    }
  }
}

// 切换表单填写可填写状态
function switchEnable(disable: boolean = false) {
  const inputs = Array.from(document.getElementsByTagName('input'))

  if (disable) {
    for (let i of inputs) {
      i.setAttribute('disabled', 'false')
    }
  } else {
    for (let i of inputs) {
      i.removeAttribute('disabled')
    }
  }
}

onMounted(() => {
  showNode.value!.width = calculate(sizeW) - lineWidth
  showNode.value!.height = calculate(sizeH) - lineWidth
  showCtx = <CanvasRenderingContext2D>showNode.value!.getContext('2d')
  showCtx.lineWidth = lineWidth

  document.addEventListener('keydown', changeDirection)

})

</script>

<style lang="scss" scoped>
canvas {
  border: 1px dashed;
}
</style>